Nesta análise vamos computar o tempo de resposta de diferentes queries que serão utilizadas na API do AQE.
Existem quatro tipos de queries principais que serão avaliadas, com algumas variações:
Consulta termos de uma determinada fonte
collection.find({"source": "TULSA_THESAURUS"})
collection.find({"source": ["TULSA_THESAURUS"]})
Consulta termos relacionados a um determinado texto
collection.find({"text": "coral"})
collection.find({"text": { "$regex": ".*coral.*" } })
Consulta termos relacionados a um determinado texto e com um tipo de relacionamento
collection.find({"text": "coral", "terms.termRelation": "RT"})
collection.find({"text": "coral", "terms.termRelation": { "$in": ["RT", "NT"]}})
collection.find({"text": { "$regex": ".*coral.*"}, "terms.termRelation": { "$all": ["RT", "NT"]}})
Consulta termos relacionados a um determinado texto com uma quantidade de passos no grafo
collection.aggregate([
{
'$match': {
'text': {
'$in': [
'coral'
]
},
'terms': {
'$ne': []
}
}
}, {
'$graphLookup': {
'from': 'termsBase',
'startWith': '$terms.text',
'connectFromField': 'terms.text',
'connectToField': 'text',
'as': 'relatedTerms',
'depthField': 'depth',
'maxDepth': p
}
}
])
import os
import time
from pathlib import Path
from pymongo import MongoClient, TEXT
import pandas as pd
import plotly.express as px
BASE_DIR = Path().resolve().parent
os.chdir(BASE_DIR)
from conf.config import settings
client = MongoClient(settings.MONGODB_URI)
collection = client[settings.MONGODB_DATABASE_NAME][settings.MONGODB_COLLECTION_NAME]
depth_step = 0
Para rodar todas as consultas da melhor maneira possível, é necessário criar os índices antes de inserir os dados. Para isso, no compass rode o seguinte comando:
db.termsBase.createIndex({text: "text"}, { language_override: "none"}, unique: true)
Vamos agora definir as queries e seus atributos e computar o tempo de resposta rodando 50 vezes cada query.
queries = [
{
"query": {"source": "TULSA_THESAURUS"},
"query_type": "find",
"classe_query": "Busca por source",
"detalhe_query": "Permite outras fontes"
},
{
"query": {"source": ["TULSA_THESAURUS"]},
"query_type": "find",
"classe_query": "Busca por source",
"detalhe_query": "Exclusivo da fonte especificada"
},
{
"query": {"text": "coral"},
"query_type": "find",
"classe_query": "Busca por termo",
"detalhe_query": "Match absoluto"
},
{
"query": {"text": { "$regex": ".*coral.*"}},
"query_type": "find",
"classe_query": "Busca por termo",
"detalhe_query": "Match com regex"
},
{
"query": {"text": "coral", "terms.termRelation": "RT"},
"query_type": "find",
"classe_query": "Busca por termo e relação",
"detalhe_query": "Permite outras relações"
},
{
"query": {"text": "coral", "terms.termRelation": { "$in": ["RT", "NT"]}},
"query_type": "find",
"classe_query": "Busca por termo e relação",
"detalhe_query": "Match de pelo menos um na lista"
},
{
"query": {"text": { "$regex": ".*coral.*"}, "terms.termRelation": { "$all": ["RT", "NT"]}},
"query_type": "find",
"classe_query": "Busca por termo e relação",
"detalhe_query": "Match de todos na lista"
},
{
"query": [
{
'$match': {
'text': {
'$in': [
'coral'
]
},
'terms': {
'$ne': []
}
}
}, {
'$graphLookup': {
'from': 'termsBase',
'startWith': '$terms.text',
'connectFromField': 'terms.text',
'connectToField': 'text',
'as': 'relatedTerms',
'depthField': 'depth',
'maxDepth': 0
}
}
],
"query_type": "aggregate",
"classe_query": "Busca por termos caminhando no grafo",
"detalhe_query": "Caminha 2 arestas do grafo"
},
{
"query": [
{
'$match': {
'text': {
'$in': [
'coral'
]
},
'terms': {
'$ne': []
}
}
}, {
'$graphLookup': {
'from': 'termsBase',
'startWith': '$terms.text',
'connectFromField': 'terms.text',
'connectToField': 'text',
'as': 'relatedTerms',
'depthField': 'depth',
'maxDepth': 1
}
}
],
"query_type": "aggregate",
"classe_query": "Busca por termos caminhando no grafo",
"detalhe_query": "Caminha 3 arestas do grafo"
},
{
"query": [
{
'$match': {
'text': {
'$in': [
'coral'
]
},
'terms': {
'$ne': []
}
}
}, {
'$graphLookup': {
'from': 'termsBase',
'startWith': '$terms.text',
'connectFromField': 'terms.text',
'connectToField': 'text',
'as': 'relatedTerms',
'depthField': 'depth',
'maxDepth': 2
}
}
],
"query_type": "aggregate",
"classe_query": "Busca por termos caminhando no grafo",
"detalhe_query": "Caminha 4 arestas do grafo"
},
{
"query": [
{
'$match': {
'text': {
'$in': [
'coral'
]
},
'terms': {
'$ne': []
}
}
}, {
'$graphLookup': {
'from': 'termsBase',
'startWith': '$terms.text',
'connectFromField': 'terms.text',
'connectToField': 'text',
'as': 'relatedTerms',
'depthField': 'depth',
'maxDepth': 3
}
}
],
"query_type": "aggregate",
"classe_query": "Busca por termos caminhando no grafo",
"detalhe_query": "Caminha 5 arestas do grafo"
}
]
df_data = {
"classe_query": list(),
"detalhe_query": list(),
"classe_detalhada": list(),
"num_docs_resposta": list(),
"num_relacoes": list(),
"tempo_resposta_ms": list(),
"iteração": list()
}
for query in queries:
for i in range(50):
if query["query_type"] == "find":
query_function = collection.find
elif query["query_type"] == "aggregate":
query_function = collection.aggregate
start = time.time()
res = list(query_function(query["query"]))
end = time.time()
tempo_resposta_ms = (end - start) * 1000
num_docs = len(res)
num_relacoes = 0
for doc in res:
num_relacoes += len(doc["terms"])
if "relatedTerms" in doc.keys():
num_relacoes += sum([len(rt["terms"]) for rt in doc["relatedTerms"]])
df_data["classe_query"].append(query["classe_query"])
df_data["detalhe_query"].append(query["detalhe_query"])
df_data["classe_detalhada"].append(query["classe_query"] + " - " + query["detalhe_query"])
df_data["num_docs_resposta"].append(num_docs)
df_data["num_relacoes"].append(num_relacoes)
df_data["tempo_resposta_ms"].append(tempo_resposta_ms)
df_data["iteração"].append(i + 1)
tempo_resposta_df = pd.DataFrame(df_data)
tempo_resposta_df.head()
| classe_query | detalhe_query | classe_detalhada | num_docs_resposta | num_relacoes | tempo_resposta_ms | iteração | |
|---|---|---|---|---|---|---|---|
| 0 | Busca por source | Permite outras fontes | Busca por source - Permite outras fontes | 12167 | 69364 | 243.000269 | 1 |
| 1 | Busca por source | Permite outras fontes | Busca por source - Permite outras fontes | 12167 | 69364 | 414.038181 | 2 |
| 2 | Busca por source | Permite outras fontes | Busca por source - Permite outras fontes | 12167 | 69364 | 177.009106 | 3 |
| 3 | Busca por source | Permite outras fontes | Busca por source - Permite outras fontes | 12167 | 69364 | 285.044670 | 4 |
| 4 | Busca por source | Permite outras fontes | Busca por source - Permite outras fontes | 12167 | 69364 | 255.010605 | 5 |
Antes de analisar o tempo de resposta das queries, vejamos a quantidade de documentos retornados por elas:
data_viz = tempo_resposta_df.groupby(
"classe_detalhada"
).agg(
{"num_docs_resposta": "mean"}
).reset_index()
fig = px.bar(
data_viz, x='classe_detalhada', y='num_docs_resposta', log_y=True,
title="Quantidade de documentos por consulta",
labels={
"classe_detalhada": "Consulta",
"num_docs_resposta": "Quantidade de documentos",
}
)
fig.show()
Podemos ver que grande parte dos documentos retornam apenas um documento, exceto a busca por source e a que utiliza regex.
Vejamos agora a quantidade de relações retornadas por cada query.
data_viz = tempo_resposta_df.groupby(
"classe_detalhada"
).agg(
{"num_relacoes": "mean"}
).reset_index()
fig = px.bar(
data_viz, x='classe_detalhada', y='num_relacoes', log_y=True,
title="Quantidade de termos relacionados por consulta",
labels={
"classe_detalhada": "Consulta",
"num_relacoes": "Quantidade de termos relacionados",
}
)
fig.show()
Podemos ver que a quantidade de termos relacionados está proporcional a quantidade de documentos, exceto pelas queries que caminham no grafo, onde apenas um documento possui centenas de relacionamentos.
Vejamos agora o tempo de resposta das queries. Vamos iniciar vendo o tempo de resposta por classe de query.
fig = px.box(
tempo_resposta_df, x="classe_query", y="tempo_resposta_ms",
title="Tempo de resposta por classe de consulta",
labels={
"classe_query": "Classe de consulta",
"tempo_resposta_ms": "Tempo de resposta (ms)",
}
)
fig.show()
Podemos ver que a busca por source é a que mais demora, tendo em vista que recupera todos os documentos do Tesauro de Tulsa. Mesmo a query retornando na casa de 10 mil documentos, ela retornou os resultados em menos de um milissegundo.
Vejamos agora o tempo de resposta de cada query.
fig = px.box(
tempo_resposta_df, x="classe_detalhada", y="tempo_resposta_ms", color="classe_query", boxmode="overlay",
title="Tempo de resposta por consulta",
labels={
"classe_detalhada": "Consulta",
"tempo_resposta_ms": "Tempo de resposta (ms)",
"classe_query": "Classe de consulta"
},
height=500
)
fig.show()
Podemos ver que o dentre as queries que não buscam todos os documentos de um determinado source, as que mais demoram são a que busca tanto por termo quanto por relação, seguido da que busca apenas o termo por regex. A eficiência das queries que caminham no grafo surpreendem, pois apesar de terem documentos com até milhares de relacionamentos, tiveram um tempo de resposta inferior as demais citadas acima.
Nesta análise pudemos ver que as queries realizadas no mongo, independente de sua complexidade, retornam em um tempo bastante razoável. As queries que mais demoraram tiveram a sua demora associada a um número alto de documentos retornados.